#include <bits/stdint-uintn.h>
#include <stdint.h>
#include "mg_typedef.h"
#include "message_loop/message_loop.h"
#include <fcntl.h>
#include <sys/ioctl.h>
#if defined( __linux__ )
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#elif defined( __VXWORKS__ )
#include <CAN/wnCAN.h>
#endif

static const char CANDEVICENAME[] = "/esdpci_200_0/0";
static struct OutsideCanPortAttr {
  int32_t            controllerFd_;
  int32_t            channelReadFd_;
  int32_t            channelWriteFd_;

  int8_t             is_inited_;
  struct sockaddr_in sock_addr_;
  struct ev_loop*    loop_;
} gOutsideCanPortAttr;

static void readMessageHandler( EV_P_ ev_io* w, int revents ) {
  int32_t            length = 0;
  struct sockaddr_in addr;
  socklen_t          addr_len = sizeof( addr );
  WNCAN_CHNMSG       buf;

  length = read( w->fd, &buf, sizeof( buf ) );
  if ( length <= 0 ) {
    MG_DEBUG( "recved data err length=%d,errno=%d\n", length, errnoGet( ) );
    return;
  }
  if ( buf.extId == FALSE ) {
    MG_DEBUG( "recved a base frame,data err length=%d,errno=%d\n",
              length,
              errnoGet( ) );
    return;
  }
  uint16_t nodeID = ( buf.id & 0xffff00 ) >> 8;
  /**
   * @brief We should define a Maroc or Enum from DevicesOutside2Inside.xml
   * directly in future.
   */
  switch ( nodeID ) {
  case 0x6204:
    /*<Device nodeID="0x6204" name="艏部舵机CAN1" part="外部设备转内部节点"> */
    break;
  default:
    break;
  }
}
/** */
static void outsideCanPortMainTask( ) {
  WNCAN_CHNMSG    txdata;
  WNCAN_CHNCONFIG chncfg;
  int             fdCtrlr, fdChan1, fdChan2;
  char            chnname[32];
  UCHAR           txChan1;
  UCHAR           txChan2;
  fdCtrlr = open( CANDEVICENAME, O_RDWR, 0 );
  if ( fdCtrlr == ERROR ) {
    MG_DEBUG( "can't open\n" );
    return;
  }
  gOutsideCanPortAttr.controllerFd_ = fdCtrlr;
  /* START the controller!! */
  ioctl( fdCtrlr, WNCAN_HALT, 0 );
  /* enable read channel */
  if ( ioctl( fdCtrlr, WNCAN_RXCHAN_GET, ( int )&txChan1 ) != OK ) {
    goto errexit;
  }
  snprintf( chnname, 32, "%s/%d", CANDEVICENAME, txChan1 );
  fdChan1 = open( chnname, O_RDONLY, 0 );
  if ( fdChan1 == ERROR ) {
    MG_DEBUG( "can't open channel\n" );
    goto errexit;
  }
  gOutsideCanPortAttr.channelReadFd_ = fdChan1;
  //  TODO(kuoted): 需根据具体情况配置；
  /* configure the channel */
  chncfg.flags = WNCAN_CHNCFG_LCLFILTER;
  //  5 bits of the 29 bits frame-id.
  chncfg.filter.mask     = 0x1F000000;
  chncfg.filter.extended = FALSE;
  ioctl( fdChan1, WNCAN_CHNCONFIG_SET, ( int )&chncfg );
  ioctl( fdChan1, WNCAN_CHN_ENABLE, 1 );
  /* enable write channel*/
  if ( ioctl( fdCtrlr, WNCAN_TXCHAN_GET, ( int )&txChan2 ) != OK ) {
    MG_DEBUG( "Failed to get channel." );
    goto errexit;
  }
  snprintf( chnname, 32, "%s/%d", CANDEVICENAME, txChan2 );
  fdChan2 = open( chnname, O_WRONLY, 0 );
  if ( fdChan2 == ERROR ) {
    MG_DEBUG( "can't open channel\n" );
    goto errexit;
  }
  gOutsideCanPortAttr.channelWriteFd_ = fdChan2;
  ioctl( fdChan2, WNCAN_CHN_ENABLE, 1 );

  {
    static ev_io    io_read_watcher;
    struct ev_loop* loop = ev_loop_new( 0 );
    ev_io_init( &io_read_watcher, readMessageHandler, fdChan1, EV_READ );
    ev_run( loop, 0 );
  }
errexit:
  if ( gOutsideCanPortAttr.channelReadFd_ != 0 ) {
    close( gOutsideCanPortAttr.channelReadFd_ );
  }
  if ( gOutsideCanPortAttr.channelWriteFd_ != 0 ) {
    close( gOutsideCanPortAttr.channelWriteFd_ );
  }
  if ( gOutsideCanPortAttr.controllerFd_ ) {
    close( gOutsideCanPortAttr.controllerFd_ );
  }
  return;
}
void aOutsideCanPortInit( ) {
#if defined( __linux__ )
  static pthread_t ptid;
  pthread_create( &ptid, 0, outsideCanPortMainTask, 0 );
#else
  taskSpawn( "tInSideCanTsk",
             100,
             0,
             512,
             open_can_port,
             0,
             0,
             0,
             0,
             0,
             0,
             0,
             0,
             0,
             0 );
#endif
}
int32_t aOutsideCanPortSend( WNCAN_CHNMSG txdata1 ) {
  int32_t ret = -1;
  ret         = write( gOutsideCanPortAttr.channelWriteFd_,
               ( char* )&txdata1,
               sizeof( txdata1 ) );
  return ret;
}
void aOutsideCanPortClose( ) {
  int32_t ret = -1;
  int32_t st  = -1;
  /* wait until the transmit queue is empty */
  do {
    ret = ioctl( gOutsideCanPortAttr.channelWriteFd_, FIONWRITE, ( int )&st );
    if ( ret != OK ) {
      break;
    }
    if ( st != 0 ) {
      break;
    }
    taskDelay( 5 );
  } while ( 1 );
  if ( gOutsideCanPortAttr.channelReadFd_ >= 1 ) {
    close( gOutsideCanPortAttr.channelReadFd_ );
    gOutsideCanPortAttr.channelReadFd_ = -1;
  }
  if ( gOutsideCanPortAttr.channelReadFd_ >= 1 ) {
    close( gOutsideCanPortAttr.channelReadFd_ );
    gOutsideCanPortAttr.channelReadFd_ = -1;
  }
  if ( gOutsideCanPortAttr.controllerFd_ >= 1 ) {
    close( gOutsideCanPortAttr.controllerFd_ );
    gOutsideCanPortAttr.controllerFd_ = -1;
  }
}